import random
import math
class UserBasedCF:
    def __init__(self,train_file,test_file):
        self.train_file = train_file
        self.test_file = test_file
        self.readData()
    def readData(self):
        #读取文件，并生成用户-物品的评分表和测试集
        self.train = dict()     #用户-物品的评分表
        for i in range(self.train_file.shape[0]):  
            user=self.train_file[i][0]   #userid train_file
            item=self.train_file[i][1]   #item_id 
            score=self.train_file[i][2]  #rating
            self.train.setdefault(user,{}) 
            self.train[user][item] = int(score)
            
        self.test = dict()      #测试集    
        for i in range(self.test_file.shape[0]):  
            user=self.test_file[i][0]   #userid test_file
            item=self.test_file[i][1]   #item_id 
            score=self.test_file[i][2]  #rating
            self.test.setdefault(user,{}) 
            self.test[user][item] = int(score)


    def UserSimilarity(self):
        #建立物品-用户的倒排表
        self.item_users = dict()
        for user,items in self.train.items():
            for i in items.keys():
                if i not in self.item_users:
                    self.item_users[i] = set()
                self.item_users[i].add(user)

        #计算用户-用户相关性矩阵
        C = dict()  #用户-用户共现矩阵
        N = dict()  #用户产生行为的物品个数
        for i,users in self.item_users.items():
            for u in users:
                N.setdefault(u,0)
                N[u] += 1
                C.setdefault(u,{})
                for v in users:
                    if u == v:
                        continue
                    C[u].setdefault(v,0)
                    C[u][v] += 1

        #计算用户-用户相似度，余弦相似度
        self.W = dict()      #相似度矩阵
        for u,related_users in C.items():
            self.W.setdefault(u,{})
            for v,cuv in related_users.items():
                self.W[u][v] = cuv / math.sqrt(N[u] * N[v])
        return self.W

    #给用户推荐前N个最感兴趣的物品
    def Recommend(self,user,K,N=20):
        rank = dict() #记录 user 的推荐物品（没有历史行为的物品）和兴趣程度
        action_item = self.train[user].keys()     #用户 user 播放的 item 和兴趣评分 
        for v,wuv in sorted(self.W[user].items(),key=lambda x:x[1],reverse=True)[0:K]: #使用与用户最相关的K个用户进行计算
            for i,rvi in self.train[v].items():
                if i in action_item:
                    continue
                rank.setdefault(i,0)
                rank[i] += wuv * rvi
        return dict(sorted(rank.items(),key=lambda x:x[1],reverse=True)[0:N])   #推荐结果的取前N个
    
    def RecallAndPrecision(self,train=None,test=None,K=3,N=20):
        train = train or self.train
        test = test or self.test
        hit = 0
        recall = 0
        precision = 0
        for user in train.keys():
            tu = test.get(user,{})
            rank = self.Recommend(user,K=K,N=N)
            for i,_ in rank.items():
                if i in tu:
                   hit += 1
            recall += len(tu)
            precision += N
        recall = hit / (recall * 1.0)
        precision = hit / (precision * 1.0)
        return (recall,precision)
